const fs = require('fs');
const { URL } = require('url');
const readline = require('readline');

const UI = require('./src/musicUI');
const Spark = require('./src/Spark');
const LLMs = require('./src/LLMs');
const KookBot = require('./src/kookbot');
const KookVoice = require('./src/kookVoice');
const ctrler = require('./src/musicController');

const { KookConfig, LLMConfigs, NeteaseConfig, targetID } = JSON.parse(fs.readFileSync('./APIconfig.json'));
class Client extends KookBot {
    UIMsgId = null;
    playListUIMsgId = null;
    Voice = null;
    constructor(KookConfig, LLMConfigs, NeteaseConfig, targetID) {
        super(KookConfig.BOT_TOKEN, KookConfig.BOT_ID);
        this.NeteaseConfig = NeteaseConfig;
        this.LLMConfigs = LLMConfigs;

        this.targetID = targetID;
        let map = new Map();
        map.set('dj', ['speed', 'volume', 'pitch', 'loop']);
        map.set('all', ['noparams']);
        this.paramMap = map;

        this.UIMsgId = null;
        this.playListUIMsgId = null;

        this.LLM = new LLMs(LLMConfigs);

        this._lastReconnectTime = null;
        this._autoReconnect = setInterval(async () => {
            let hour = new Date().getHours();
            if (hour === this._lastReconnectTime) return;

            await this.reconnectWebsocket().catch(err => console.log(err));
            if (hour === 22) this.sendMsg("(met)2216804103(met) 签到", this.targetID);

            this._lastReconnectTime = hour;
        }, 1800_000);
    }

    async getVoiceInstance(guild_id, user_id) {
        return new Promise(async (resolve, reject) => {
            try {
                let res = await this.getJoinChannelID(guild_id, user_id);
                if (!res || !res.id) reject("NOT_JOIN");
                let wsUrl = await this.getVoiceWsURL(res.id);
                if (!wsUrl) reject("NO_WSURL");
                res = await KookVoice.createInstance(wsUrl);
                resolve(res);
            } catch (err) {
                reject(err);
            };
        })
    };

    async _getResFromLLMs(content, user_id, callback, model) {
        return this.LLM.getReply(content, user_id, callback, model);
    }

    _ctrlerInit(guild_id, user_id) {
        return new Promise((resolve, reject) => {
            this.getJoinChannelID(guild_id, user_id)
                .then(res => {
                    if (!res || !res.id) reject("NOT_JOIN");
                    if (!this.ctrler) {
                        this.ctrler = new ctrler(this.getVoiceWsURL.bind(this),
                            res.id, this.NeteaseConfig);
                        resolve();
                    }
                })
                .catch(err => reject(err));
        })
    }

    async msgHandler(data) {
        return new Promise(async (resolve, reject) => {
            let msg_parse_res = await this.msgParse(data).catch(err => console.log(err))
            if (!msg_parse_res) { resolve(); return };
            switch (msg_parse_res.type) {
                case 'event':
                    this._eventHandler(msg_parse_res.id, data)
                        .then(res => resolve(res))
                        .catch(err => reject(err));
                    break;
                case 'default':
                    const { cmd, content, paramArr, id } = msg_parse_res;
                    this.actionDispenser(id, cmd, content, paramArr)
                        .then(fn => {
                            if (typeof fn !== "function") { resolve(); return };
                            fn(id, content, paramArr)
                                .then(res => resolve(res))
                                .catch(err => reject(err))
                        })
                        .catch(err => reject(err));
                    break;
                default:
                    reject("RefErr: Unknown msg_parse_res.type: " + msg_parse_res.type)
            }

        })
    }

    async actionDispenser(id, cmd, content, paramArr) {
        return new Promise(async (resolve, reject) => {
            if (['ui'].includes(cmd))
                if (!content) { resolve(this._actions.getUI); return }

            if (['ping', '我测'].includes(cmd))
                if (!content) { resolve(this._actions.getPing); return }

            if (['help', '?', '？', '帮助'].includes(cmd))
                if (!content) { resolve(this._actions.getHelp); return }

            if (['gemini'].includes(cmd))
                { resolve(this._actions.getReplyByModel); return }
            if (!this.ctrler) {
                await this._ctrlerInit(id.guild, id.user)
                    .then(() => {
                        this.ui = new UI(this.ctrler.Emitter);
                        this.ctrler.on("UI_CHANGE", async () => {
                            if (!this.playListUIMsgId) return;
                            if (this.ctrler.PlayList.length <= 0) {
                                this.deleteMsgById(this.playListUIMsgId)
                                    .then(() => this.playListUIMsgId = null)
                                    .catch(err => console.log(err));
                            } else {
                                // this.updateMsgById(this.playListUIMsgId,
                                //     UI.getPlaylistFormat(this.ctrler.PlayList, this.ctrler.MusicNow));
                                this.updateMsgById(this.playListUIMsgId,
                                    this.ui.getMainPlayerFormat(this.ctrler.MusicNow));
                            };
                        });
                        // this.ctrler.on("PLAYING_CHANGE", async () => {
                        //     // console.log(JSON.stringify(this.ctrler.MusicNow));
                        //     const music = this.ctrler.MusicNow.music;
                        //     if (!music || !music.name || !music.artists) return;
                        //     this.postNowPlayingStatus(music.artists[0] ?? "NULL", music.name)
                        //         .catch(err => console.log(err));
                        // });
                    })
                    .catch(err => {
                        if (err === 'NOT_JOIN')
                            resolve(this.sendMsg('根没加语音频道😡', id.target));
                        else reject(err);
                    });
            };

            if (['add', '+', '添加', '导入'].includes(cmd))
                if (content) { resolve(this._actions.importSong); return }

            if (['clear', '清空', '清空列表'].includes(cmd))
                if (!content) { resolve(this._actions.clearPlayList); return }

            if (['shuffle', 'random', '打乱', '随机'].includes(cmd))
                if (!content) { resolve(this._actions.shufflePlayList); return }

            if (['prev', '>', ']', '》', '上一首', '上一曲'].includes(cmd))
                if (!content) { resolve(this._actions.playPrev); return }

            if (['next', '<', '[', '《', '下一首', '下一曲'].includes(cmd))
                if (!content) { resolve(this._actions.playNext); return }

            if (['pause', 'stop', '暂停', '停止', '别放了'].includes(cmd))
                if (!content) { resolve(this._actions.pausePlay); return }

            if (['resume', 'continue', '继续', '播放', '接着放'].includes(cmd))
                if (!content) { resolve(this._actions.resumePlay); return }

            resolve(this.msgParseDefault.bind(this));
        });
    }

    _actions = {
        getUI: async (id) => {
            if (!this.ctrler || !this.ctrler.PlayList || !this.ctrler.PlayList.length) {
                return this.sendMsg('根没有曲😡', id.target)
            }
            return new Promise((resolve, reject) => {
                if (this.playListUIMsgId) {
                    this.deleteMsgById(this.playListUIMsgId)
                        .then(() => resolve())
                        .catch(err => reject(err));
                };
                this.sendMainUI(this.ctrler.PlayList, this.ctrler.MusicNow, id.target)
                    .then(res => {
                        this.playListUIMsgId = res.msg_id;
                        resolve(res);
                    })
                    .catch(err => reject(err));
            })
        },

        getPing: async (id) => {
            return new Promise((resolve, reject) => {
                const instance = new Spark(this.LLMConfigs.SparkConfig);
                instance.Ping()
                    .then(async res => {
                        this.sendMsg(`Spark连接成功，延迟${res}ms!`, id.target)
                            .then(res => resolve(res)).catch(err => reject(err));
                    })
                    .catch(err => {
                        this.sendMsg("捏么 没连上😡 " + err, id.target)
                            .catch(err => reject(err))
                            .then(res => resolve(res));
                    });
            })
        },

        getHelp: async (id) => {
            return this.sendMsg(this.helpMsg, id.target, 9);
        },

        getReplyByModel: async (id, content) => {
            return this.msgParseDefault(id, content, 'gemini');
        },

        importSong: async (id, content, paramArr) => {
            let res = this._validateImportContent(content);
            if (!res) return this.msgParseDefault(id, content);
            if (res === 'wrong') return this.sendMsg("路径不对劲 加不了😡", id.target);

            return new Promise((resolve, reject) => {
                paramArr = this.paramParse(paramArr, 'dj');
                if (Object.keys(paramArr).length > 0)
                    this.sendMsg(`参数为${JSON.stringify(paramArr)}`, id.target)
                        .catch(err => reject(err));

                switch (res.type) {
                    case 'name':
                        this.ctrler.addToPlayListByMusicName(res.content)
                            .then(musicData => {
                                this.sendMsg(`已添加《${musicData.name}》到播放列表!`, id.target)
                                    .then(() => resolve(musicData))
                                    .catch(err => reject(err));
                            })
                            .catch(err => {
                                switch (err) {
                                    case 'NO_RESULT': case 'NO_DATA':
                                        this.sendMsg("没找着 哈哈😀", id.target)
                                            .catch(err => reject(err));
                                        break;
                                    default:
                                        reject(err);
                                }
                            }
                            );
                        break;
                    case 'song':
                        this.sendMsg("还不会 哈哈😀", id.target)
                            .then(() => resolve(res)).catch(err => reject(err));
                        break;
                    case 'album':
                        this.sendMsg("还不会 哈哈😀", id.target)
                            .then(() => resolve(res)).catch(err => reject(err));
                        break;
                    case 'playlist':
                        this.ctrler.importPlayListById(res.content)
                            .then(res => {
                                this.sendMsg(`已添加歌单到播放列表!`, id.target)
                                    .then(() => resolve(res)).catch(err => reject(err));
                            })
                            .catch(err => reject(err));
                        break;
                    case 'video':
                        this.sendMsg("还不会 哈哈😀", id.target)
                            .then(() => resolve(res)).catch(err => reject(err));
                        break;
                    default:
                        reject('RefErr: Null res.type.');
                };
            })
        },

        clearPlayList: async (id) => {
            return new Promise((resolve, reject) => {
                if (!this.ctrler || !this.ctrler.PlayList || !this.ctrler.PlayList.length) {
                    this.sendMsg('根没有曲😡', id.target)
                        .then(res => resolve(res)).catch(err => reject(err));
                };
                this.ctrler.clearPlayList()
                    .then(res => {
                        this.sendMsg("清空了 哈哈😀", id.target)
                            .catch(err => reject(err))
                            .then(() => resolve(res));
                    })
                    .catch(err => reject(err));
            })
        },

        shufflePlayList: async (id) => {
            return new Promise((resolve, reject) => {
                this.ctrler.shufflePlayList()
                    .then(res => {
                        this.sendMsg("打乱了 哈哈😀", id.target)
                            .then(() => resolve(res)).catch(err => reject(err));
                    })
                    .catch(err => reject(err));
            });
        },

        playPrev: async (id) => {
            return new Promise((resolve, reject) => {
                this.ctrler.playPrevMusic()
                    .then((res) => {
                        let msg;
                        if (!res) msg = '没有上一首了 哈哈😀';
                        msg = msg ?? `上一首: ${JSON.stringify(res.name)}`;
                        this.sendMsg(msg, id.target)
                            .then((res) => resolve(res)).catch(err => reject(err));
                    })
                    .catch(err => reject(err));
            });
        },

        playNext: async (id) => {
            return new Promise((resolve, reject) => {
                this.ctrler.playNextMusic()
                    .then((res) => {
                        let msg;
                        if (!res) msg = '没有下一首了 哈哈😀';
                        msg = msg ?? `下一首: ${JSON.stringify(res.name)}`;
                        this.sendMsg(msg, id.target)
                            .then((res) => resolve(res)).catch(err => reject(err));
                    })
                    .catch(err => reject(err));
            });
        },

        pausePlay: async (id) => {
            return new Promise((resolve, reject) => {
                if (!this.ctrler) {
                    this.sendMsg("跟没点歌😡", id.target)
                        .then(() => resolve()).catch(err => reject(err));
                };

                if (!this.ctrler.playFlag) {
                    this.sendMsg("跟没放😡", id.target)
                        .then(() => resolve()).catch(err => reject(err));
                };

                this.ctrler.stopPlay()
                    .then(res => {
                        this.sendMsg("停了 哈哈😀", id.target)
                            .then(() => resolve(res)).catch(err => reject(err));
                    })
                    .catch(err => reject(err));
            })
        },

        resumePlay: async (id) => {
            return new Promise((resolve, reject) => {
                if (!this.ctrler) {
                    this.sendMsg("跟没点歌😡", id.target)
                        .then(() => resolve()).catch(err => reject(err));
                }

                if (this.ctrler.playFlag) {
                    this.sendMsg("放着呢😡", id.target)
                        .then(() => resolve()).catch(err => reject(err));
                }

                this.ctrler.startPlay()
                    .then(res => {
                        this.sendMsg("开了 哈哈😀", id.target)
                            .catch(err => reject(err))
                            .then(() => resolve(res));
                    })
                    .catch(err => reject(err));

            });
        }

    }

    async msgParse(data) {
        return new Promise((resolve, reject) => {
            this.target_id = data.target_id;
            if (data.type === 255) {
                const body = data.extra.body;
                const id = { guild: body.guild_id, user: body.user_id, target: body.target_id, msg: body.msg_id };
                resolve({ type: "event", id });
                return;
            };
            const id = { guild: data.extra.guild_id, user: data.author_id, target: data.target_id };

            let content = data.content;
            const is_atBot_regExp = new RegExp(/^\(met\)(.+?)\(met\)(.+)/i);
            let dataArr = content.match(is_atBot_regExp);

            if (!dataArr || dataArr[1] !== this.botID.toString()) { resolve; return }
            content = dataArr[2].trim();

            const cmd_pattern
                = new RegExp(/^(帮助|添加|清空|打乱|下一首|导入|播放|停止|开始|UI|Ping|gemini)(?:\s|$)*([^-]+)*/i);
            const param_pattern
                = new RegExp(/-(noparams|speed|volume|pitch|loop)[:=]\S+(\s|$)*?/g);

            const match_data = content.match(cmd_pattern);

            if (!match_data) {
                this.msgParseDefault(id, content + ' ')
                    .then(() => resolve)
                    .catch(err => reject(err));
            } else {
                const cmd = match_data[1] ? match_data[1].toLowerCase().trim() : null;
                const msg_content = match_data[2] ? match_data[2].trim() : null;
                const paramArr = content.match(param_pattern);
                console.log(`Cmd: ${cmd}, Content: ${msg_content}`)
                resolve({ type: "default", cmd, content: msg_content, paramArr, id });
            }
        });
    }

    async msgParseDefault(id, content, model='spark') {
        return new Promise(async (resolve, reject) => {
            const { msg_id } = await this.sendMsg("🤔", id.target, 9).catch(err => reject(err));
            let music;

            const callback = async (msg) => {
                return new Promise((resolve, reject) => {
                    if (!music) {
                        music = this._getAnonMusicName(msg);
                        if (music) {
                            this.actionDispenser(id, 'add', music)
                                .then(func => {
                                    func(id, music)
                                        .then(resolve())
                                        .catch(err => reject(err));
                                })
                                .catch(err => reject(err))
                        }
                    }
                    this.updateMsgById(msg_id, msg)
                        .then(() => resolve())
                        .catch(err => reject(err));
                });

            };

            this._getResFromLLMs(content ?? ' ', id.user, callback, model)
                .then(res => {
                    this.updateMsgById(msg_id, `(met)${id.user}(met) \n${res.content}\n(spl)Token消耗${res.token}(spl)`)
                        .then(res => {
                            console.log("[LLMs] SparkAPI 回答完成!");
                            resolve(res);
                        })
                        .catch(err => reject(err));
                })
                .catch(err => reject(err));
        })
    }

    paramParse(paramArr, cmd) {
        let ret = [];
        if (paramArr) {
            for (let paramStr of paramArr) {
                let param = {};
                let arr = paramStr.match(/^-(.+?)[:=](.+?)(\s|$)/);
                if (!arr || !arr[1] || !arr[2]) continue;
                if (this.paramMap.get('all').indexOf(arr[1]) === -1)
                    if (!this.paramMap.get(cmd) || this.paramMap.get(cmd).indexOf(arr[1]) === -1)
                        continue;
                param[arr[1]] = arr[2];
                ret.push(param);
            }
        }
        return ret;
    }

    async _eventHandler(id, data) {
        switch (data.extra.type) {
            case 'message_btn_click':
                const value = JSON.parse(data.extra.body.value);
                console.log("[DEBUG] eventParse recieved:", value);
                if (!this.ctrler) return Promise.reject("NO_CTRLER");
                this.ui.configHandler(value);
                switch (value.action) {
                    case "playlist-remove":
                        if (!this.playListUIMsgId) this.playListUIMsgId = id.msg;
                        else if (id.msg != this.playListUIMsgId) return Promise.resolve();
                        return this.ctrler.removeFromPlayListByMusicId(value.musicId);
                    case "play-prev":
                        return this._actions.playPrev(id);
                    case "stop":
                        return this._actions.pausePlay(id);
                    case "start":
                        return this._actions.resumePlay(id);
                    case "play-next":
                        return this._actions.playNext(id);
                    case "unfold-playlist":
                        return this.sendMsg("还不会 哈哈😀", id.target);
                    default:
                        break;
                }

                break;
            default:
                return Promise.resolve();
        }
    }

    _getAnonMusicName(str) {
        if (typeof str !== 'string') return null;
        if (str.indexOf('唱') === -1) return null;
        let start = str.indexOf('《');
        let end = str.indexOf('》');
        if (start !== -1 && end !== -1 && start < end) {
            return str.substring(start + 1, end);
        }
        return null;
    }

    _validateImportContent(str, force) {
        if (str.length > 250) str = str.substring(0, 250);
        if (force) return { type: 'name', content: str };
        if (str.indexOf('bili') === -1 && str.indexOf('music.163') === -1) {
            return { type: 'name', content: str }; // 可能是歌名
        }

        const regex = /([\[\s]|^)+(.+?)([\]\s]|$)+/g;
        let match_str = str.match(regex);
        if (match_str && match_str[0]) {
            match_str = match_str[0].replace(/\/#/g, '').trim();
            match_str = match_str.substring(match_str.charAt(0) === '[', match_str.length - 1 - (match_str.charAt(match_str[0].length - 1) === ']'))
        }

        str = match_str ?? str;
        const url = new URL(str);
        const host_name = url.hostname;
        const path_name = url.pathname;
        const params = url.searchParams;
        if (!host_name || !path_name)
            return false; // 可能是打错了

        let type, content;

        switch (host_name) {
            case "music.163.com":
                switch (path_name) {
                    case '/song':
                        type = 'song'; break;
                    case '/album':
                        type = 'album'; break;
                    case '/playlist':
                        type = 'playlist'; break;
                    default:
                        return false;
                }
                content = params.get('id');
                if (!content) return false; break;

            case "www.bilibili.com": case "m.bilibili.com":
                const regex = /^\/video\/BV([0-9a-zA-Z]+)[$/]*/;
                type = 'video';
                content = path_name.match(regex)[1];
                if (!content) return false;
                break;
            default:
                return 'wrong';
        }
        return { type, content };
    }

    async sendMainUI(playlist_arr, music_now, target_id) {
        // return this.sendCardMsg(UI.getPlaylistFormat(playlist_arr, music_now), target_id);
        return this.sendCardMsg(this.ui.getMainPlayerFormat(music_now), target_id);
    }

}
const client = new Client(KookConfig, LLMConfigs, NeteaseConfig, targetID);

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.on('line', (input) => {
    switch (input.substring(0, 4)) {
        case 'chou':
            client.sendMsg("(met)2216804103(met) 十连", targetID).catch(err => console.log(err.data.data));
            break;
        case 'qian':
            client.sendMsg("(met)2216804103(met) 签到", targetID).catch(err => console.log(err.data.data));
            break;
        case 'yuns':
            client.sendMsg("(met)2216804103(met) 运势", targetID).catch(err => console.log(err.data.data));
            break;
        case 'beib':
            client.sendMsg("(met)2216804103(met) 背包", targetID).catch(err => console.log(err.data.data));
            break;
        case 'tiqv':
            client.sendMsg("(met)2216804103(met) 提取 Lechi66 全部", targetID).catch(err => console.log(err.data.data));
            break;
        case 'send':
            cmd = input.slice(5);
            client.sendMsg(cmd, targetID);
            break;
        case 'exit':
            rl.close();
            break;
        default:
            console.log('[MAIN] Received: ' + input);
    }
});

rl.on('close', async () => {
    client.exit = true;
    console.log('[MAIN] Exiting...');
    if (client.targetID) await client.sendMsg("😭 ---深刻なエラーが 発生しました--- 😭", targetID);
    await client.offLine();
    if (client.ctrler && client.ctrler.playFlag) await client.ctrler.stopPlay();
    if (client.wsClient) await client.wsClient.close();
    if (client.UIMsgId) await client.deleteMsgById(client.UIMsgId);
    if (client.playListUIMsgId) await client.deleteMsgById(client.playListUIMsgId);
    if (client.Voice) {
        client.Voice.Voice.exitVoiceChannel()
            .then(() => { client.Voice.client.close() })
            .catch(async err => { console.log(err); await client.Voice.client.close() });
    }
    process.exit(0);
});

client.connectWebSocket(
    client.sendMsg("😎 ---世界で一番のお姫様 闪亮登场--- 😎", targetID).catch(err => console.log(err.data.data))
);













// class loadingUI {
//     constructor(sendMsg, refreshMsg, target_id, msg) {
//         this._target = target_id;
//         this._msg = msg;
//         this._sendMsg = sendMsg;
//         this._refreshMsg = refreshMsg;
//     }

//     get msgId() {
//         return this._msgId;
//     }
//     async start() {
//         return new Promise((resolve, reject) => {
//             this._sendMsg(this._msg, this._target)
//                 .then(res => {
//                     let num = 0;
//                     this._msgId = res.msg_id;
//                     this._interval = setInterval(async () => {
//                         num = ++num > 6 ? 0 : num;
//                         this._refreshMsg(this._msgId, this._msg + ".".repeat(num))
//                             .catch(err => reject(err));
//                     }, 500);
//                 })
//                 .catch(err => reject(err));
//         });
//     }
//     async stop() {
//         if (!this._interval) return Promise.reject("RefErr: Null Interval.");
//         clearInterval(this._interval);
//         return this._refreshMsg(this._msgId);
//     };

// }

